// 将所有的模块代码要按照一定得顺序引入
// ======================Tools============================
;(function (window,undefined) {
  // 制作一个工具对象，内部添加多种工具的方法
  var Tools = {
    // 获取一个范围内部的随机整数
    getRandom: function (min, max) {
      min = Math.ceil(min);
      max = Math.floor(max);
      return Math.floor(Math.random() * (max - min + 1)) + min; //含最大值，含最小值
    },
    // 获取随机颜色的方法
    getColor: function () {
      // rgb(r,g,b)  三个色值的颜色可以随机获取 0-255 之间的数字
      // 获取三个色值
      var r = this.getRandom(0, 255);
      var g = this.getRandom(0, 255);
      var b = this.getRandom(0, 255);
      // 返回一个 颜色值
      return "rgb(" + r + "," + g + "," + b + ")";
    }
  };
  window.Tools = Tools;
})(window,undefined)
// ==================Food===========================
;(function (window,undefined) {
  // 全局的变量
  var ps = "absolute";
  // 创建 食物 的构造函数
  function Food(option) {
    // 避免传入的参数数据类型不对，或者没有传参
    option = option instanceof Object ? option : {};
    // 传入的数据可能是类似数组等对象，所以需要进一步判断
    this.width = option.width || 20;
    this.height = option.height || 20;
    this.x = option.x || 0;
    this.y = option.y || 0;
    this.color = option.color || "green";
    // 增加一个属性，存储将来这个对象渲染出来的所有 div 元素
    this.elements = [];
  }
  // 渲染一个元素到页面之上，需要添加到原型对象的方法中
  Food.prototype.render = function (map) {
    // 创建一个新的 div 元素
    var ele = document.createElement("div");
    // 每次设置样式之前，都随机获取一个 x 和 y 的值
    this.x = Tools.getRandom(0, map.clientWidth / this.width - 1) * this.width;
    this.y = Tools.getRandom(0, map.clientHeight / this.height - 1) * this.height;
    // 添加对应的样式
    ele.style.width = this.width + "px";
    ele.style.height = this.height + "px";
    ele.style.left = this.x + "px";
    ele.style.top = this.y + "px";
    ele.style.backgroundColor = this.color;
    ele.style.position = ps;
    // 让新元素添加到指定的父级中
    map.appendChild(ele);
    // 将新元素添加的 数组中，方便后期调用删除
    this.elements.push(ele);
  };
  // 删除一个食物 div 元素
  Food.prototype.remove = function (map, i) {
    // 可以通过一些方法获取要被删除的食物的下标
    // 将元素 从 html结构中删除
    map.removeChild(this.elements[i]);
    // 将元素 从 数组中删除
    this.elements.splice(i, 1);
  };
  // 利用 window 对象暴露 Food 函数可以给外部使用
  window.Food = Food;
})(window,undefined)
// ===================Snake=============================
;(function (window,undefined) {
  // 全局变量
  var ps = "absolute";
  // 创建 蛇 的构造函数
  function Snake(option) {
    // 避免传入的参数数据类型不对，或者没有传参
    option = option instanceof Object ? option : {};
    // 给对象添加属性
    // 设置蛇节的宽度和高度属性
    this.width = option.width || 20;
    this.height = option.height || 20;
    // 设置蛇身的数据
    this.body = [
      {x: 3,y: 2,color: "red"},
      {x: 2,y: 2,color: "blue"},
      {x: 1,y: 2,color: "blue"}
    ];
    // 设置蛇移动的方向,还可以设置为 left 、top 、bottom
    this.direction = "right";
    // 添加一个元素的数组，存储所有渲染的 div
    this.elements = [];
  }
  // 添加一个将元素渲染到页面上的方法
  Snake.prototype.render = function (map) {
    // 生成对应个数的 div 元素
    // 遍历数组
    for (var i = 0,len = this.body.length ; i < len ; i++) {
      // 根据数组的每一项的数据生成一个新的 div 元素
      var piece = this.body[i];
      // 创建新元素
      var ele = document.createElement("div");
      // 添加样式
      ele.style.width = this.width + "px";
      ele.style.height = this.height + "px";
      ele.style.left = piece.x * this.width + "px";
      ele.style.top = piece.y * this.height + "px";
      ele.style.position = ps;
      ele.style.backgroundColor = piece.color;
      // 渲染到指定的父级内部
      map.appendChild(ele);
      // 将添加的新元素存在数组里
      this.elements.push(ele);
    }    
  };
  // 添加 蛇 运动的方法
  Snake.prototype.move = function () {
    // 蛇身的每一节都要变成上一节的位置
    // 循环需要从最后一项开始，为了避免前面的数据提前发生变化
    for (var i = this.body.length - 1 ; i > 0 ; i--) {
      this.body[i].x = this.body[i-1].x;
      this.body[i].y = this.body[i-1].y;
    }
    // 存储一下蛇头的数据
    var head = this.body[0];
    // 蛇头要根据方向发生位置变化
    switch (this.direction) {
      case "right":
        head.x += 1;
        break;
      case "left":
        head.x -= 1;
        break;
      case "top":
        head.y -= 1;
        break;
      case "bottom":
        head.y += 1;
    }
  };
  // 删除上一次渲染的蛇的所有div元素
  Snake.prototype.remove = function (map) {
    // 遍历数组删除所有元素
    // 将元素从html结构中删掉
    for (var i = this.elements.length - 1 ; i >= 0  ; i--) {
      map.removeChild(this.elements[i]);
    }
    // 数组也需要进行清空
    this.elements = [];
  }
  // 通过 window 暴露构造函数
  window.Snake = Snake;
})(window,undefined)
// ========================Game===================
;(function (window,undefined) {
  // 定义一个全局变量，存储 this 
  var that;
  // 创建一个 游戏 的构造函数
  function Game(map) {
    // 设置三个属性，存储 食物、 蛇、地图
    this.food = new Food();
    this.snake = new Snake();
    this.map = map;
    that = this;
  }
  // 添加一个游戏开始的方法，方法内初始化蛇和食物
  Game.prototype.start = function () {
    // 1.添加蛇和食物到 地图上
    this.food.render(this.map);
    this.food.render(this.map);
    this.food.render(this.map);
    this.snake.render(this.map);
    // 2.让游戏逻辑开始
    // 2.1 让蛇自动运动起来
    runSnake();
    // 2.2 通过上下左右箭头控制蛇的运动方向
    bindKey(); 

  }
  // 封装一个私有函数，控制上下左右按键更改的方向
  function bindKey() {
    // 给文档绑定键盘按下事件
    document.onkeydown = function (e) {
      // console.log(e.keyCode);
      // 键盘的编码
      // 37 -- left
      // 38 -- top
      // 39 -- right
      // 40 -- bottom
      switch (e.keyCode) {
        case 37:
          that.snake.direction = "left";
          break;
        case 38:
          that.snake.direction = "top";
          break;
        case 39:
          that.snake.direction = "right";
          break;
        case 40:
          that.snake.direction = "bottom";
          break;
      }
    };
  }
  // 封装一个私有函数，这个函数只能在模块内部进行调用
  function runSnake() {
    // 开启一个定时器，让蛇连续运动起来
    var timer = setInterval(function () {
      // 定时器函数内部的 this 指向的是 window
      // 让蛇运动起来    
      that.snake.move();
      // 删掉上一次的蛇
      that.snake.remove(that.map);
      // 渲染新位置的蛇
      that.snake.render(that.map);
      // 记录一下最大的位置
      var maxX = that.map.offsetWidth / that.snake.width;
      var maxY = that.map.offsetHeight / that.snake.height;
      // 找到当前蛇头的位置
      var headX = that.snake.body[0].x;
      var headY = that.snake.body[0].y;
      // 每一次蛇走到新的位置，都要判断一下是否吃到食物了
      // 2.3 判断蛇头与食物是否碰撞，吃掉食物 ，让自己增加一节
      // 记录一下食物的坐标
      // var foodX = that.food.x;
      // var foodY = that.food.y;
      // 获取蛇头的具体坐标位置，px值
      var hX = headX * that.snake.width;
      var hY = headY * that.snake.height;
      // 判断
      // 将食物的数组中每一个都要进行对比，谁被吃掉，删除自己，渲染一个新的元素
      for (var i = 0 ; i < that.food.elements.length ; i++) {
        if (that.food.elements[i].offsetLeft === hX && that.food.elements[i].offsetTop === hY) {
          // 吃到了食物
          // 让食物删除，然后渲染一个新的食物
          that.food.remove(that.map,i);
          that.food.render(that.map);
          // 添加一个新的蛇节
          var last = that.snake.body[that.snake.body.length - 1];
          that.snake.body.push({
            x: last.x,
            y: last.y,
            color: last.color
          });
        }
      }
      // 每移动一次，都要判断是否出了地图，游戏是否结束
      // 2.4 判断是否超出地图范围，结束游戏      
      // 进行判断
      if (headX < 0 || headX >= maxX || headY < 0 || headY >= maxY) {
        // 停止定时器
        clearInterval(timer);
        // 弹出提醒
        alert("Game over");
      }
    }, 150);
  }
  // 将构造函数通过 window 暴露
  window.Game = Game;
})(window,undefined)
// ========================== Main =========================
;(function (window,undefined) {
  var map = document.getElementById("map");
  var game = new Game(map);
  game.start();
})(window,undefined)